Genesis 3D |
||
per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto! |
Dodicesima
lezione
Per commentare, dare suggerimenti
o consigli (molto apprezzati) e per segnalare eventuali errori o
disattenzioni (sempre possibili), puoi inviare una email all'autore (clicca
sul nome sopra). Grazie per la collaborazione!
In questo tutorial vedremo
come:
1) prelevare dalla mappa le
entity
2) interagire con
l'ambiente
3) inserire un actor
4) inserire un suono
localizzato
Quando abbiamo creato la mappa
del nostro esempio, abbiamo aggiunto alcuni oggetti fittizi, chiamate entità.
Una entità contiene una serie di informazioni utili al programmatore per
posizionare oggetti nello spazio di stato, e per aggiungere effetti molto
interessanti, in modo quasi automatico.
Ecco cosa dice la
documentazione ufficiale a proposito:
The
entity subsystem allows you to get design time data from a level that was edited
with GEDIT. The data is streamed in from the level file so that the programmer
is not required to do any post editing, runtime linking of structure
elements.
Si tratta di un metodo per
linkare alla mappa dell'ambiente alcune informazioni che poi possono essere
lette in fase di esecuzione.
Alcune delle entità "standard"
sono la DeathMatchStart, Door, Light, DinamicLight, Corona ecc... Vedremo che è
possibile usare anche delle entità "user defined".
I comandi per leggere le entità
sono essenzialmente 3:
geEntity_EntitySet
geWorld_GetEntitySet(geWorld
*World, const char *SetName)
Dato il puntatore dell'oggetto
geWorld chiediamo di estrarre una lista di entità di un certo tipo. SetName
indica il nome della categoria di entità che vogliamo
estrarre.
geEntity
*geEntity_EntitySetGetNextEntity(geEntity_EntitySet *EntitySet,
geEntity *Entity)
Mediante questa funzione si
scorre la lista di entity. EntitySet è il valore fornito dalla funzione
geWorld_GetEntitySet. Mentre Entity è un puntatore
ad una entity. Il valore restituito sarà l'entity successiva a quella indicata.
se Entity è NULL viene restituita la prima Entity nella lista. Se non ci sono
più entity viene restituito NULL.
void
*geEntity_GetUserData(geEntity
*Entity)
Questa funzione si occupa di
prelevare le informazioni relative all'entità. Poiché viene restituito un
puntatore generico (void *) per poter assegnare l'area di memoria puntata ad un
puntatore della struttura dati opportuna è necessario effettuare un cast
esplicito. Vedremo un esempio tra breve.
Con questa funzione rileviamo
l'informazione sulla posizione iniziale del player. L'entità che andiamo a
interrogare è DeathMatchStart, la cui definizione si trova nel file entity.h ed
è:
//
CameraStartPosition -------------
#pragma
GE_Type("Player.ico")
typedef
struct weOrigineCamera
{
#pragma
GE_Published
geVec3d
Origin;
#pragma
GE_Origin(Origin)
}
DeathMatchStart;
La spiegazione dei comandi
#pragma li vedremo dopo. Per adesso notiamo che l'unico campo è Origin, un
vettore nello spazio di stato.
Vediamo la
funzione:
geBoolean
GetStartingPosition(geVec3d *posizione)
{
geEntity_EntitySet *lEntitySet;
geEntity
*lEntity;
DeathMatchStart
*camera;
geBoolean
lettura = GE_FALSE;
lEntitySet
= geWorld_GetEntitySet(World, "DeathMatchStart");
Viene prelevata la lista di
entity di tipo "DeathMatchStart", quindi si preleva il primo elemento della
lista.
if
(lEntitySet) {
lEntity = geEntity_EntitySetGetNextEntity(lEntitySet, NULL); // Get the
first DeathMatchStart
if (lEntity) {
camera = (DeathMatchStart *) geEntity_GetUserData(lEntity); // Get the
UserActor from the entity
*posizione =
camera->Origin;
lettura = GE_TRUE;
}
}
return
lettura;
}
Tramite una conversione
esplicita a puntatore (DeathMatchStart *) possiamo accedere al campo dati Origin
che leggiamo e forniamo come parametro di ritorno della
funzione.
Come ci suggerisce il nome
stesso l'entità Door serve per animare la porte di un ambiente. La definizione
standard delle Door è la seguente:
//
Door
#pragma
GE_Type("Model.ico")
typedef
struct Door
{
#pragma
GE_Published
geWorld_Model *Model;
geVec3d
Origin;
#pragma
GE_Origin(Origin)
} Door;
Gli unici campi sono Model, un
puntatore ad un modello dell'ambiente e Origin che indica la posizione della
porta.
Per dimostrare l'estendibilità
del sistema, proviamo ad inserire una serie di informazioni
supplementari.
//
Door ------------------------
#pragma
GE_Type("Model.ico")
typedef
struct weDoor
{
#pragma
GE_Published
float
DistanceToOpen; // Max
Distance person has to be away from the door to open it.
geBoolean
Automatic; //
Indica se la porta si apre automaticamente
geBoolean
Locked;
// Indica se la porta può essere aperta
geWorld_Model *Model;
geVec3d
Origin;
#pragma
GE_Origin(Origin)
#pragma
GE_DefaultValue(DistanceToOpen, "200.0")
#pragma
GE_DefaultValue(Automatic, "True")
#pragma
GE_DefaultValue(Locked, "False")
} ExtDoor;
////////////////////////////////
Aggiungiamo quindi una
variabile "DistanceToOpen" che indica che se la porta è automatica, qual'è la
distanza alla quale avvicinandoci la porta si apre.
Altro campo è "Automatic" che
serve a determinare se la porta è automatica, oppure se per aprirla dobbiamo
interagire con essa.
Locked indica invece se la
porta è aperta o chiusa a chiave. Potremmo anche indicare un numero ID della
chiave che apre questa porta ma le possibilità di espansione sono limitate solo
dalla fantasia del programmatore.
Vediamo quindi anche il
significato delle direttive #pragma.
#pragma GE_Type("Model.ico"):
serve a specificare l'icona da usare nel world editor per indicare tale
entity
#pragma GE_Published: serve ad
indicare quali sono i campi da visualizzare nell'entity editor, ovvero i campi
manipolabili direttamente nell'editor.
#pragma GE_Origin(Origin):
questo comando molto utile serve per assegnare automaticamente ad un campo (nel
nostro caso origin) il valore del vettore dell'entity. Un modo comodo per
settare punti nello spazio 3d.
#pragma
GE_DefaultValue(DistanceToOpen, "200.0"): alcuni valori possono essere settati a
dei valori standard.
#pragma
GE_Documentation(Automatic, "spedifica il tipo di porta"): aggiunge la
spiegazione del campo dati nell'entity editor.
Adesso che abbiamo creato la
nostra entity dobbiamo informare il World Editor della posizione in cui leggere
il file .H nella quale essa si trova. Questo può essere fatto andando a
modificare le opzioni di livello, nel campo Headers. Bisogna stare attenti che
il programma non gradisce una definizione multipla. Assicurarsi quindi che il
file in cui si trova la DeathMatchStart non sia nella stessa directory
dell'enity che vogliamo far leggere. Oppure dobbiamo togliere il riferimento
alla directory con le definizioni standard (operazione non
consigliata).
Possiamo quindi procedere
creando una directory "Entity" all'interno della quale mettere un file .H che
contiene tale descrizione.
Adesso agendo con il World
Editor aggiungere una enitity di tipo ExtDoor (il tipo che abbiamo appena
creato) e modificare i campi secondo fantasia, oppure lasciare inalterato il
valore di default. Quindi compilare il livello.
Prima di capire come funziona
questa funzione ecco la struttura dati usata per realizzare una lista. Ogni
porta presente nel livello viene memorizzata in un nodo di tale lista. In questo
modo poi per effettuare l'update di ogni porta basta scorrere
l'elenco.
typedef
struct
NodoPorta
{
float
CurrentPos; // Time
currently at in animation.
float
TimeAllowedOpen;// Time which the door is allowed to be open
for.
float
DistanceToOpen; // Max
Distance person has to be away from the door to open it.
geBoolean
Automatic; //
Indica se la porta si apre automaticamente
geBoolean
Locked;
// Indica se la porta può essere aperta
geWorld_Model *Model;
geVec3d
Origin;
geBoolean
moving;
struct
NodoPorta
*prossimo;
}
NodoPorta;
NodoPorta
*lista_porta = NULL;
La maggior parte dei campi sono
quelli della struttura ExtDoor che abbiamo creato. Altri campi come CurrentPos,
moving e prossimo servono per la gestione dell'animazione e della
lista.
void
GetDoorInfo() {
geEntity_EntitySet *Set=NULL; //
conterra' info sull'insieme delle entita' door
geEntity
*Entity=NULL;
// conterra' info su una entita'
NodoPorta
*corrente = NULL;
Door
*temp;
Set =
geWorld_GetEntitySet(World, "ExtDoor");
Viene prelevato l'EntitySet
relativa all'entità che abbiamo definito noi. Mediante un ciclo while scorriamo
la lista di entity e preleviamo i dati relativi.
if
(Set) {
Entity = geEntity_EntitySetGetNextEntity(Set,NULL);
while (Entity) {
temp = (Door*) geEntity_GetUserData(Entity);
if (corrente
== NULL) {
lista_porta = malloc(sizeof(NodoPorta));
corrente = lista_porta;
} else {
corrente->prossimo = malloc(sizeof(NodoPorta));
corrente = corrente->prossimo;
}
Quindi aggiungiamo un nodo alla
nostra lista di porte e modifichiamo i campi di tale nodo in modo che
rispecchiano i valori letti nella entity.
corrente->Automatic
= temp->Automatic;
corrente->DistanceToOpen =
temp->DistanceToOpen;
corrente->Locked
= temp->Locked;
corrente->Model
= temp->Model;
corrente->Origin
= temp->Origin;
corrente->moving = GE_FALSE;
corrente->CurrentPos = 0.0f;
corrente->prossimo = NULL;
Entity
= geEntity_EntitySetGetNextEntity(Set,Entity);
}
}
}
Dopo aver richiamato tale
funzione avremo una lista di porte, il cui primo nodo è puntato da
lista_porta.
Adesso che abbiamo letto le informazioni
relative alle porte presenti nell'ambiente è necessario richiamare ad ogni loop
nel MainLoop la funzione che aggiorna la posizione delle varie porte prima del
rendering.
Il parametro speed indica la velocità di
apertura delle porte.
void
UpdateDoor(float speed) {
NodoPorta
*corrente = lista_porta;
geMotion
*Motion;
gePath *Path;
geXForm3d
Dest;
// questa matrice conterra' la matrice di trasformazione per la
porta
float
dist;
float
tStart,tEnd;
while
(corrente) {
dist =
geVec3d_DistanceBetween(&pl.posizione,&corrente->Origin);
Per prima cosa scorriamo la
lista. Per ogni nodo rileviamo la distanza tra la posizione del giocatore e
quella della porta. La distanza ci serve per le porte automatiche. Se la
distanza è inferiore a quella fissata dal parametro DistanceToOpen la porta deve
essere aperta. Per le porte non automatiche invece viene controllato il tasto
SHIFT.
// fase 1 verifica se la porta e' vicina al
giocatore
if
((!corrente->Locked) && (corrente->Automatic ||
io.keys[VK_SHIFT]))
if (dist <= corrente->DistanceToOpen) {
corrente->moving = GE_TRUE;
}
Se la porta deve essere aperta,
o se si sta già aprendo, dobbiamo usare il comando geWorld_OpenModel che serve per comunicare all'engine che
durante il rendering il modello "porta" potrebbe mostrare ciò che sta
dietro.
// aggiorna il movimento della porta
if
(corrente->moving) {
// informa
l'engine di mostrare quello che sta dietro
geWorld_OpenModel(World,
corrente->Model, GE_TRUE);
Motion = geWorld_ModelGetMotion(corrente->Model);
Path = geMotion_GetPath(Motion,
0);
Adesso vengono prelevati gli
oggetti geMotion e gePath. Spieghiamo brevemente di che si
tratta.
Ecco cosa dice la
documentazione ufficiale a proposito di geMotion
The
Motion module provides support for creating and maintaining lists of named Paths
and associated time-indexed events, and methods to sample the animation paths at
any arbitrary time.
Mentre per quanto riguarda
gePath:
The
Path module provides support for creating and maintaining time indexed keyframe
data, and sampling the path at arbitrary times.
In pratica una animazione
abbiamo visto che è formata una serie di matrici di trasformazioni, associate ad
un parametro temporale chiamato keyframe. I keyframe non sono necessariamente
contigui tra di loro. Per conoscere la posizione intermedia tra due keyframe si
agisce per interpolazione. Questa operazione è svolta dall'oggetto
gePath.
L'oggetto geMotion permette di
associare ad un oggetto 3d (sia esso un modello o un actor) una lista di gePath,
i quali possono essere usati indipendentemente, oppure mixati assieme per
ottenere animazioni più complesse.
Nel nostro caso è presente
un'unica animazione, per cui non abbiamo molte
alternative.
corrente->CurrentPos
+= speed;
geMotion_GetTimeExtents(Motion,
&tStart, &tEnd);
Questa funzione,
geMotion_GetTimeExtents, fornisce il valore temporale associato al primo e
all'ultimo keyframe, ovvero le estensioni temporali dell'animazione
stessa.
if
(corrente->CurrentPos >= tEnd) {
corrente->CurrentPos = 0.0f;
corrente->moving = GE_FALSE;
} else {
gePath_Sample(Path,
corrente->CurrentPos, &Dest);
geWorld_SetModelXForm(World,
corrente->Model, &Dest);
}
}
corrente = corrente->prossimo;
}
}
Quindi effettuiamo il controllo
dell'animazione. Se il frame corrente si trova entro start ed end
allora l'animazione deve essere effettuata. Altrimenti vuol dire che è
finita.
Per effettuare l'animazione dobbiamo estrarre
la matrice di trasformazione opportuna mediante gePath_Sample ed applicarla al
modello mediante geWorld_SetModelXForm.
La documentazione relativa agli actor si
trova inserita nel file actor.h; ecco riportata una parte:
/* Actor
This object is designed to support character
animation.
There are two basic objects to deal with.
Actor Definition (geActor_Def)
A geActor_Def embodies the geometry (polygon, and bone information),
and a library of motions that can be applied to that
geometry.
Actor
A geActor is an instance of an actor definition. The definition is used for
the geometry, but all additional settings, such as the bone pose,
lighting information,
and cuing information is unique for a geActor.
Gli actor sono stati pensati per realizzare
animazione di oggetti nella nostra scena. Ci sono due oggetti con i quali
interagire: i geActor_Def che contengono la descrizione della geometria
dell'actor (poligoni, bones e motions) e i geActor che rappresentano delle
istanze di un geActor_Def. Ogni geActor condivide le informazioni sulla
geometria mentre le informazioni sulle bones, sull'illuminazione e su altri
controlli sono unici.
Il grande vantaggio dell'uso degli actor sta
infatti nella possibilità di definire delle animazioni molto precise su una
struttura gerarchica. Gli actor sono memorizzati in file esterni in formato ACT
nella quale è contenuta sia l'informazione sulla geometria che
sull'animazione.
In questo tutorial vedremo come caricare un
actor e animarlo.
Allo scopo di gestire l'animazione di un
actor creiamo una struttura dati che chiamiamo Gimnic, in quanto il nostro actor
è una donna che fa ginnastica.
typedef
struct {
geActor
*actor;
geVec3d
posizione;
geVec3d
angolo;
int
animation;
float CurrentPos;
int
coda[4];
}
Gimnic;
Gimnic
gimnic;
Il primo campo è un puntatore
all'oggetto geActor corrispondente. I seguenti due campi contengono informazioni
sulla posizione e sulla angolazione dell'actor rispetto
all'ambiente.
Il campo animation serve per
selezionare quale animazione è attualmente in corso, mentre CurrentPos contiene
il numero di frame corrente per l'animazione stessa.
L'array coda è un modo semplice
per memorizzare ripetizioni di animazione in sequenza. Ogni cella dell'array
contiene un numero che corrisponde all'animazione in corso. La variabile
animation è l'indice di tale array.
void
LoadGimnic() {
geVFile
*ActorFile = NULL;
geActor_Def
*ActorDef = NULL;
gimnic.actor =
NULL;
//load the act
file.
ActorFile =
geVFile_OpenNewSystem(NULL, GE_VFILE_TYPE_DOS, "Actor\\gimnic.act", NULL,
GE_VFILE_OPEN_READONLY);
if(ActorFile)
{
//create a definition of the actor
ActorDef =
geActor_DefCreateFromFile (ActorFile);
La funzione
geActor_DefCreateFromFile svolge il ruolo di caricare il file ACT e di
memorizzare le informazioni in un oggetto geActor_Def, che ci servirà da stampo
per creare i nostri Actor.
if(ActorDef)
{
gimnic.actor = geActor_Create (ActorDef);
Con la funzione geActor_Create
istanziamo un actor in base alla sua definizione e lo memorizziamo nella nostra
struttura dati.
//add
that actor to the world
geWorld_AddActor (World, gimnic.actor,
GE_ACTOR_RENDER_NORMAL | GE_ACTOR_COLLIDE, 0xffffffff);
Adesso non ci resta che
informare il geWorld che vogliamo usare questo actor. Sarà esso poi a gestire il
rendering opportunamente.
//make
the actor bigger.
geActor_SetScale(gimnic.actor,
4.0f,4.0f,4.0f);
Adesso possiamo effettuare
alcune semplici operazioni sull'actor, come ad esempio effettuare una operazione
di scala. Nella funzione SetupActor effettuiamo altre
inizializzazioni.
}
}
geVFile_Close(ActorFile); // Close our file.
SetupActor();
}
Lo scopo di tale funzione è chiaramente
quello di preparare il nostro actor affinché venga visualizzato correttamente
nell'ambiente.
void
SetupActor() {
geXForm3d
XForm;
if (gimnic.actor)
{
gimnic.coda[0]=1;
gimnic.coda[1]=2;
gimnic.coda[2]=2;
gimnic.coda[3]=1;
gimnic.animation = 0;
gimnic.CurrentPos = 0.0f;
Impostiamo la coda delle
animazioni in modo che vengano svolte in sequenza le animazioni 1,2,2,1 in un
loop continuo.
gimnic.angolo.X = 4.71f; // correzzione di -90
gradi
gimnic.angolo.Y = 0.0f;
gimnic.angolo.Y = 0.0f;
gimnic.posizione.X = 0;//-100;
gimnic.posizione.Y = -150;
gimnic.posizione.Z = 0;//600;
Posizioniamo l'actor
nell'ambiente e lo ruotiamo. E' chiaro che anziché utilizzare dei valori
sperimentali, si potrebbe demandare alle entity il posizionamento dell'actor
nell'ambiente.
geXForm3d_SetIdentity(&XForm);
//Setup the rotation
geXForm3d_RotateX(&XForm, gimnic.angolo.X);
geXForm3d_RotateY(&XForm, gimnic.angolo.Y);
geXForm3d_RotateZ(&XForm, gimnic.angolo.Z);
geXForm3d_Translate(&XForm, gimnic.posizione.X,
gimnic.posizione.Y,gimnic.posizione.Z);
geActor_ClearPose(gimnic.actor,
&XForm); // Posiziona
}
}
Infine impostiamo una matrice
di trasformazione e la usiamo per posizionare l'actor mediante il comando
geActor_ClearPose.
Queste due funzioni devono
essere richiamate in fase di inizializzazione, mentre la terza funzione,
UpdateActor viene usata dentro la MainLoop.
Questa funzione ha il compito
di aggiornare la posizione e il keyframe corrente per l'actor. Anche qui il
parametro speed serve per rendere più o meno veloce
l'animazione.
void
UpdateActor(geFloat speed) {
geMotion
*actor_motion = NULL;
geActor_Def
*actor_def = NULL;
geXForm3d
XForm;
geFloat start =
0.0f, end = 0.0F;
actor_def =
geActor_GetActorDef(gimnic.actor);
switch
(gimnic.coda[gimnic.animation]) {
case
1:
actor_motion = geActor_GetMotionByName(actor_def,
"piegamenti_ginocchia");
break;
case
2:
actor_motion = geActor_GetMotionByName(actor_def,
"saltello_sul_posto");
break;
}
A seconda dell'animazione
impostata nella coda, si preleva il geMotion relativo all'animazione corrente.
Il comando per effettuare tale operazione è geActor_GetMotionByName. Il nome si
riferisce a quello impostato nell'actor builder in fase di
costruzione.
if
(actor_motion) {
geMotion_GetTimeExtents(actor_motion, &start,
&end);
geXForm3d_SetIdentity(&XForm);
//Setup the rotation
geXForm3d_RotateX(&XForm, gimnic.angolo.X);
geXForm3d_RotateY(&XForm, gimnic.angolo.Y);
geXForm3d_RotateZ(&XForm, gimnic.angolo.Z);
geXForm3d_Translate(&XForm, gimnic.posizione.X,
gimnic.posizione.Y,gimnic.posizione.Z);
geActor_ClearPose(gimnic.actor, &XForm); //
Posiziona
Per prima cosa viene
posizionato l'actor in considerazione alla sua posizione e al suo
orientamento.
geActor_SetPose(gimnic.actor,
actor_motion, gimnic.CurrentPos, NULL);
Quindi viene impostato il
keyframe CurrentPos relativo alla nostra animazione. Sarà l'oggetto geActor
stesso a occuparsi di effettuare le trasformazioni relative delle singole parti
di cui è costituito, eventualmente interpolando la matrice di
trasformazione.
gimnic.CurrentPos
+= speed;
//geXForm3d_Translate(&(motore->box->posizione),0,0,1.0);
if (gimnic.CurrentPos > end) {
gimnic.animation++;
if (gimnic.animation > 3)
gimnic.animation = 0;
gimnic.CurrentPos = start;
}
}
}
Questa ultima parte di codice
serve a gestire la coda in maniera che finita una animazione si inizi
l'animazione successiva in coda e poi si ricominci da
capo.
Anche genesis ha un proprio
sistema sonoro che si "aggancia" a quello del sistema operativo. Affinché si
possano eseguire suoni è necessario inizializzare tale sistema. Questo deve
essere fatto nella funzione InitEngine che riporto per comodità di seguito. In
blu l'intera funzione, in marrone le aggiunte.
geBoolean
InitEngine(HWND hWnd)
{
GE_Rect Rect;
//For the camera object's rectangular view
geDriver_System
*DrvSys;
Engine
= geEngine_Create(hWnd, "MinApp", ".");
if
(!Engine)
{
MessageBox(hWnd, "Could not create engine object!", "MinApp Error...",
48);
return GE_FALSE;
}
geEngine_EnableFrameRateCounter(Engine, FALSE);
//This function
will enable you to use sound with your program through the Genesis
API.
SoundSys =
geSound_CreateSoundSystem(hWnd);
if
(!SoundSys)
MessageBox(hWnd, "Could not create Sound System! There will be no sound!", "No Sound...",
48);
DrvSys =
geEngine_GetDriverSystem(Engine);
if
(!DrvSys)
{
MessageBox(hWnd, "Could not get the Genesis Driver System!", "MinApp
Error...", 48);
return GE_FALSE;
}
//Set the driver
you want to use
FindDriver(DrvSys);
.
.
.
Come si può vedere si è creato
un nuovo oggetto chiamato geSound_System. Tale variabile deve essere dichiarata,
insieme ad altre tre nella main.h
geSound_System
*SoundSys; //The
Genesis Sound System
geSound_Def
*bandaDEF;
geSound
*bandaSOUND;
geVec3d
posizione_suono;
Adesso analizziamo il modo in
cui usare tale sistema.
Questa funzione si occupa di
caricare un file .wav e di memorizzarne l'handler in un puntatore che chiamiamo
bandaDEF. Questo handler contiene la descrizione di ciò che deve essere
suonato.
void
CaricaSuono() {
geVFile
*SoundFile = NULL;
SoundFile =
geVFile_OpenNewSystem(NULL,
GE_VFILE_TYPE_DOS,
"wav\\banda.wav",
NULL,
GE_VFILE_OPEN_READONLY);
if (SoundFile)
{
bandaDEF = geSound_LoadSoundDef(SoundSys,SoundFile);
Mediante la funzione
geSound_LoadSoundDef si carica nell'handler il file wav
desiderato.
posizione_suono.X = 0;
posizione_suono.Y = 0;
posizione_suono.Z = 0;
In particolare, poiché stiamo
cercando un effetto abbastanza sofisticato, forniamo una sorgente del suono,
come se questo fosse localizzato nel punto assegnato.
geSound_SetMasterVolume(SoundSys,
0.9f);
Assegniamo inoltre un volume di
riproduzione del suono. Si noti che ancora nessun suono sta per essere
riprodotto. Il valore del volume è un valore percentuale compreso tra 0.0 e 1.0
del volume totale.
}
else
bandaDEF = NULL;
}
Quello che abbiamo fatto fin
ora (e si può notare una matrice comune per molte delle operazioni svolte) è di
preparare l'oggetto suono affinché possa essere riprodotto. con questa funzione,
che richiameremo ad ogni ciclo del MainLoop, iniziamo la
riproduzione.
void
PlaySound__() {
geXForm3d
XForm;
geFloat volume,
pan, frequency;
geXForm3d_SetIdentity(&XForm);
//determina le
caratteristiche del suono in base alla posizione della
camera
geXForm3d_RotateX(&XForm, pl.angolo.X);
geXForm3d_RotateY(&XForm, pl.angolo.Y);
geXForm3d_RotateZ(&XForm, pl.angolo.Z);
geXForm3d_Translate(&XForm, pl.posizione.X, pl.posizione.Y+pl.height,
pl.posizione.Z);
geSound3D_GetConfig(World,&XForm,&posizione_suono,500.0f,2.0f,&volume,&pan,&frequency);
Con questa operazione
indichiamo al sistema sonoro di calcolare volume, frequenza e pan relativi al
suono tenendo conto della attenuazione dovuta alla distanza del player dalla
sorgente sonora.
if
(!geSound_SoundIsPlaying(SoundSys,
bandaSOUND) ) {
bandaSOUND = geSound_PlaySoundDef(SoundSys,bandaDEF,volume,pan,frequency,FALSE);
} else
{
geSound_ModifySound(SoundSys,
bandaSOUND, volume, pan, frequency);
}
}
Adesso facciamo una
differenziazione. Se il suono è già in riproduzione (ovvero se
geSound_SoundIsPlaying(SoundSys, bandaSOUND) restituisce true modifichiamo i
parametri di volume, pan e frequenza, in modo che si percepisca una attenuazione
con la distanza. se invece il suono non è ancora stato riprodotto, o è
terminato, allora viene inizializzata la riproduzione mediante il comando
ge_Sound_PlaySoundDef. La variabile geSound tiene conto dell'attuale
riproduzione del suono. C'è quindi la stessa differenza tra geSound e
geSound_Def che c'è tra geActor e geActor_Def.
Vediamo infine come sono state
modificate le due funzioni WinMain() e MainLoop() in seguito a queste
aggiunte:
int
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nShowCmd) {
InitLog();
LoadPrefs(io.driver, &io.Width,
&io.Height);
CreateMainWindow(hInstance,nShowCmd);
//Initialize the
Genesis engine
InitEngine(hWnd);
Setup();
GetDoorInfo();
LoadActor();
CaricaSuono();
MainLoop();
//Shutdown the
Genesis engine and clean up the memory
Shutdown();
//End
Program
return
1;
}
void
MainLoop() {
MSG
msg;
int
run;
geBoolean
coll;
//Main game
loop
run =
1;
while
(run)
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) { // Is There A Message
Waiting?
if (msg.message==WM_QUIT) {
// Have We Received A Quit Message?
run = 0;
// If So done=TRUE
} else {
// If Not, Deal With Window Messages
TranslateMessage(&msg);
// Translate The Message
DispatchMessage(&msg);
// Dispatch The Message
}
} else {
// If There Are No Messages
if (pl.stato == standing || pl.stato == falling) {
coll = Gravity((float)pl.caduta);
if (coll == GE_TRUE)
pl.stato =
standing;
}
// Aggiorna l'ambiente
MoveCamera();
AggiornaXForm(); // sposta la
camera
UpdateDoor(0.01f);
UpdateActor();
RiproduciSuono();
if (!geEngine_BeginFrame(Engine, Camera, GE_FALSE))
run = 0;
if (!geEngine_RenderWorld(Engine, World, Camera,
0.0f))
run = 0;
if (!geEngine_EndFrame(Engine))
run = 0;
if (io.keys[VK_ESCAPE]) {
run=0;
}
}
}
}
Con questo tutorial termina la carrellata sull'interessanti possibilità messe a disposizione da genesis. Cose di cui parlare ce ne sarebbero ancora molte. Ma si tratta di argomenti avanzati che meritano ancora un certo chiarimento anche nei confronti del sottoscritto. In ogni caso già così si sono fornite le basi per realizzare delle animazioni 3d molto belle e sofisticate.
Questo articolo è stato scaricato
dal Club di informatica |